﻿#include <algorithm>
#include <gtest/gtest.h>
#include <iostream>
#include <vector>

#include <learn/alg/alg_approximator.h>
#include <learn/alg/alg_distance.h>
#include <learn/utils/output.h>

using namespace xi;
using namespace xi::alg;
using namespace xi::geom;

TEST(TestAlgGeom, parameter)
{
    std::vector<Vec3> points = {Vec3(0.0, 0.0, 0.0), Vec3(1.0, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(0.0, 0.0, 1.0)};
    Alg_Parameter<3> param;
    // std::cout << param.equally(points) << std::endl;
    // std::cout << param.chord(points) << std::endl;
    // std::cout << param.centripetal(points) << std::endl;

    std::vector<std::vector<Vec3>> polygons;
    for (int i = 0; i < 5; i++)
    {
        std::vector<Vec3> polygon;
        for (int j = 0; j < 4; j++)
        {
            polygon.push_back(Vec3(double(i), double(j), 0.0));
        }
        polygons.push_back(polygon);
    }
    // std::cout << param.equally(polygons) << std::endl;
    // std::cout << param.chord(polygons) << std::endl;
    // std::cout << param.centripetal(polygons) << std::endl;
}

TEST(TestAlgGeom, knot)
{
    {
        std::vector<Vec3> points;
        for (int i = 0; i < 6; i++)
        {
            points.push_back(Vec3(double(i), 0.0, 0.0));
        }
        Alg_Parameter<3> param;
        Alg_Knot<3> knot;
        std::cout << knot.equally(6, 3) << std::endl;
        std::cout << knot.average(param.chord(points), 3) << std::endl;
    }

    // 测试 compat
    for (int t = 0; t < 10; t++)
    {
        std::mt19937_64 gen(std::random_device{}());
        std::uniform_real_distribution<double> dis(0.0, 1.0);
        std::vector<double> knots1, knots2;
        knots1.push_back(dis(gen));
        knots2.push_back(dis(gen));

        int p = 4, q = 3;
        for (int i = 0; i < p; i++)
            knots1.push_back(knots1.back());
        for (int i = 0; i < 15; i++)
            knots1.push_back(knots1.back() + dis(gen));
        for (int i = 0; i < p; i++)
            knots1.push_back(knots1.back());

        for (int i = 0; i < q; i++)
            knots2.push_back(knots2.back());
        for (int i = 0; i < 8; i++)
            knots2.push_back(knots2.back() + dis(gen));
        for (int i = 0; i < q; i++)
            knots2.push_back(knots2.back());

        // std::cout << "knots1: " << knots1 << std::endl;
        // std::cout << "knots2: " << knots2 << std::endl;

        std::vector<Vec3> points1, points2;
        for (int i = 0; i < knots1.size() - p - 1; i++)
            points1.push_back(random_point<3>(-1.0, 1.0));
        for (int i = 0; i < knots2.size() - q - 1; i++)
            points2.push_back(random_point<3>(-1.0, 1.0));

        auto c1 = std::make_shared<Geom_BSplineCurve<3>>(std::move(points1), std::move(knots1), p);
        auto c2 = std::make_shared<Geom_BSplineCurve<3>>(std::move(points2), std::move(knots2), q);

        Alg_Knot<3>::compat(c1, c2);

        EXPECT_EQ(c1->knot_num(), c2->knot_num());
        for (int i = 0; i < c1->knot_num(); i++)
            EXPECT_DOUBLE_EQ(c1->knot(i), c2->knot(i));
    }
}

TEST(TestAlgGeom, distance)
{
    {
        Alg_BSplineCurveEnergy_Approximator<3> curveApprox;

        curveApprox.wBending = 1e-12;
        curveApprox.wStretch = 1e-12;

        auto f = [](double x) -> Vec3 { return Vec3{x, x * x, x * x * x}; };
        for (int i = 0; i < 100; i++)
            curveApprox.data.push_back(f(i / 100.0));
        auto curve = curveApprox.approximate(30);

        std::cout << "approximated curve: " << Alg_Distance<3>::distance(curveApprox.data, curve) << std::endl;
    }

    {
        Alg_BSplineSurfaceEnergy_Approximator<3> surfaceApprox;

        surfaceApprox.wBending = 1e-3;
        surfaceApprox.wStretch = 1e-10;
        surfaceApprox.uDegree = 3;
        surfaceApprox.vDegree = 3;

        auto f = [](double u, double v) -> Vec3 { return Vec3{u, v, sin(u + v) * cos(u - v)}; };

        // 20 x 30 个采样点
        int N = 20;
        int M = 30;

        std::vector<Vec3> points(N * M);
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                surfaceApprox.data.push_back(f(5.0 / (N - 1) * i, 5.0 / (M - 1) * j));

        auto surface = surfaceApprox.approximate(10, 8);

        std::cout << "approximated surface: " << Alg_Distance<3>::distance(surfaceApprox.data, surface) << std::endl;
        std::cout << "approximated surface: " << Alg_Distance<3>::distance(surfaceApprox.data, surfaceApprox.params, surface) << std::endl;
    }
}